#include <autolock.h>
#include "condition.h"

namespace log4x
{
Condition::Condition(Mutex &mtx)
    : m_cond_mtx(mtx)
{
#ifdef _WIN32
    m_counter  = 0;
    m_sema      = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
    m_handshake = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
#else
    pthread_cond_init(&m_cond, NULL);
#endif
}

Condition::~Condition()
{
    broadcast();
#ifdef _WIN32
    CloseHandle(m_handshake);
    CloseHandle(m_sema);
#else
    pthread_cond_destroy(&m_cond);
#endif
}

bool
Condition::wait(int ms)
{
#ifdef _WIN32
    {
        AutoLock lock(m_mtx);
        ++m_counter;
    }
    m_cond_mtx.unlock();
    bool ret_s = (WaitForSingleObject(m_sema, (ms <= 0) ? INFINITE : ms) == WAIT_OBJECT_0);
    bool ret_h = !!ReleaseSemaphore(m_handshake, 1, NULL);
    m_cond_mtx.lock();
    return ret_s && ret_h;
#else
    int err;
    if (ms <= 0)
    {
        err = pthread_cond_wait(&m_cond, &(m_cond_mtx._mtx));
    }
    else
    {
        struct timespec ts;
        struct timeval  tv;
        gettimeofday(&tv, NULL);

        uint64_t usec = tv.tv_usec + ms * 1000;

        if (usec >= 1000000)
        {
            ts.tv_sec  = tv.tv_sec + usec / 1000000;
            ts.tv_nsec = usec % 1000000 * 1000;
        }
        else
        {
            ts.tv_sec  = tv.tv_sec;
            ts.tv_nsec = usec * 1000;
        }

        err = pthread_cond_timedwait(&m_cond, &(m_cond_mtx._mtx), &ts);
    }
    return (err == 0);
#endif
}

void
Condition::notify()
{
#ifdef _WIN32
    AutoLock lock(m_mtx);
    if (m_counter > 0)
    {
        ReleaseSemaphore(m_sema, 1, NULL);
        --m_counter;

        /* NOTE: will block if the main thread has exited, you'd better not call this function at static destructor of class */
        WaitForSingleObject(m_handshake, INFINITE);
    }
#else
    pthread_cond_signal(&m_cond);
#endif
}

void
Condition::broadcast()
{
#ifdef _WIN32
    AutoLock lock(m_mtx);
    if (m_counter > 0)
    {
        ReleaseSemaphore(m_sema, m_counter, NULL);
        do
        {
            --m_counter;
            WaitForSingleObject(m_handshake, INFINITE);
        }
        while (m_counter > 0);
    }
#else
    pthread_cond_broadcast(&m_cond);
#endif
}
}
